package org.groupg.practice.http;

import org.apache.http.*;
import org.apache.http.impl.DefaultBHttpClientConnection;
import org.apache.http.impl.DefaultBHttpServerConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.protocol.*;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ElementalReverseProxy {

    private static final String HTTP_IN_CONN = "http.proxy.in-conn";
    private static final String HTTP_OUT_CONN = "http.proxy.out-conn";
    private static final String HTTP_CONN_KEEPALIVE = "http.proxy.conn-keepalive";

    public static void main(final String[] args) throws Exception {
//        if (args.length < 1) {
//            System.out.println("Usage: <hostname[:port]> [listener port]");
//            System.exit(1);
//        }
        final HttpHost targetHost = HttpHost.create("localhost:3000");
        int port = 8001;
//        if (args.length > 1) {
//            port = Integer.parseInt(args[1]);
//        }

        System.out.println("Reverse proxy to " + targetHost);

        final Thread t = new RequestListenerThread(port, targetHost);
        t.setDaemon(false);
        t.start();
    }

    // 定义一个名为RequestListenerThread的静态内部类，它继承自Thread类，用于监听和处理HTTP请求
    static class RequestListenerThread extends Thread {

        // 定义目标主机、服务器套接字和HTTP服务的私有成员变量
        private final HttpHost target;
        private final ServerSocket serversocket;
        private final HttpService httpService;

        // 构造方法，接收端口号和目标主机作为参数，并初始化服务器套接字和HTTP服务
        public RequestListenerThread(final int port, final HttpHost target) throws IOException {
            this.target = target; // 设置目标主机
            this.serversocket = new ServerSocket(port); // 在指定端口上创建服务器套接字

            // 初始化用于处理传入连接的HTTP协议处理器
            final HttpProcessor inhttpproc = new ImmutableHttpProcessor(
                    new ResponseDate(), // 添加响应日期头
                    new ResponseServer("Test/1.1"), // 设置服务器信息
                    new ResponseContent(), // 设置响应内容处理器
                    new ResponseConnControl()); // 设置响应连接控制处理器

            // 初始化用于处理传出连接的HTTP协议处理器
            final HttpProcessor outhttpproc = new ImmutableHttpProcessor(
                    new RequestContent(), // 设置请求内容处理器
                    new RequestTargetHost(), // 设置目标主机处理器
                    new RequestConnControl(), // 设置请求连接控制处理器
                    new RequestUserAgent("Test/1.1"), // 设置用户代理信息
                    new RequestExpectContinue(true)); // 设置是否期望继续接收数据

            // 初始化用于执行传出请求的HTTP请求执行器
            final HttpRequestExecutor httpexecutor = new HttpRequestExecutor();

            // 初始化用于处理传入请求的处理器映射
            final UriHttpRequestHandlerMapper reqistry = new UriHttpRequestHandlerMapper();
            reqistry.register("*", new ProxyHandler( // 注册一个处理器，用于处理所有传入的请求
                    this.target, // 设置目标主机
                    outhttpproc, // 设置用于传出连接的HTTP协议处理器
                    httpexecutor)); // 设置HTTP请求执行器

            // 初始化HTTP服务，用于处理传入的HTTP请求
            this.httpService = new HttpService(inhttpproc, reqistry);
        }

        // 重写Thread类的run方法，定义线程的执行逻辑
        @Override
        public void run() {
            System.out.println("Listening on port " + this.serversocket.getLocalPort()); // 输出监听端口信息
            while (!Thread.interrupted()) { // 在线程未被中断时持续监听和处理请求
                try {
                    final int bufsize = 8 * 1024; // 设置缓冲区大小
                    // 初始化传入的HTTP连接，并等待客户端连接
                    final Socket insocket = this.serversocket.accept();
                    final DefaultBHttpServerConnection inconn = new DefaultBHttpServerConnection(bufsize); // 创建服务器连接对象
                    System.out.println("Incoming connection from " + insocket.getInetAddress()); // 输出客户端连接信息
                    inconn.bind(insocket); // 将服务器连接对象与客户端套接字绑定

                    // 初始化传出的HTTP连接（这里并未实际使用，可能是为了后续扩展或与其他代码协同工作）
                    final DefaultBHttpClientConnection outconn = new DefaultBHttpClientConnection(bufsize);

                    // 创建一个新的工作线程来处理该请求，并启动该线程
                    final Thread t = new ProxyThread(this.httpService, inconn, outconn); // 创建新线程对象，传入HTTP服务、服务器连接和客户端连接对象作为参数
                    t.setDaemon(true); // 设置线程为守护线程，随主线程退出而退出
                    t.start(); // 启动线程以处理请求
                } catch (final InterruptedIOException ex) { // 捕获中断异常，中断循环并退出线程
                    break;
                } catch (final IOException e) { // 捕获其他I/O异常，并输出错误信息后中断循环并退出线程
                    System.err.println("I/O error initialising connection thread: " + e.getMessage());
                    break;
                }
            }
        }
    }

    // 定义一个静态类ProxyHandler，它实现了HttpRequestHandler接口
    static class ProxyHandler implements HttpRequestHandler {

        // 定义了一些私有成员变量
        private final HttpHost target;             // 目标主机
        private final HttpProcessor httpproc;       // HTTP处理器
        private final HttpRequestExecutor httpexecutor; // HTTP请求执行器
        private final ConnectionReuseStrategy connStrategy; // 连接复用策略

        // 构造函数，初始化上述的成员变量
        public ProxyHandler(
                final HttpHost target,
                final HttpProcessor httpproc,
                final HttpRequestExecutor httpexecutor) {
            super();
            this.target = target;
            this.httpproc = httpproc;
            this.httpexecutor = httpexecutor;
            // 设置默认的连接复用策略
            this.connStrategy = DefaultConnectionReuseStrategy.INSTANCE;
        }

        // 重写了HttpRequestHandler接口的handle方法
        @Override
        public void handle(
                final HttpRequest request,
                final HttpResponse response,
                final HttpContext context) throws HttpException, IOException {

            // 从上下文中获取HTTP连接
            final DefaultBHttpClientConnection conn = (DefaultBHttpClientConnection) context.getAttribute(
                    HTTP_OUT_CONN);

            // 检查连接是否打开或是否过时，如果是，则创建一个新的Socket连接
            if (!conn.isOpen() || conn.isStale()) {
                final Socket outsocket = new Socket(this.target.getHostName(), this.target.getPort() >= 0 ? this.target.getPort() : 80);
                conn.bind(outsocket);
                System.out.println("Outgoing connection to " + outsocket.getInetAddress());
            }

            // 在上下文中设置HTTP连接和目标主机
            context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
            context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, this.target);

            // 打印请求的URI
            System.out.println(">> Request URI: " + request.getRequestLine().getUri());

            // 移除一些逐跳头部
            request.removeHeaders(HTTP.TARGET_HOST);
            // ... (其他头部的移除操作)

            // 预处理请求，执行请求，并进行后处理
            this.httpexecutor.preProcess(request, this.httpproc, context);
            final HttpResponse targetResponse = this.httpexecutor.execute(request, conn, context);
            this.httpexecutor.postProcess(response, this.httpproc, context);

            // 移除目标响应中的一些逐跳头部
            targetResponse.removeHeaders(HTTP.CONTENT_LEN);
            // ... (其他头部的移除操作)

            // 设置响应的状态行、头部和实体
            response.setStatusLine(targetResponse.getStatusLine());
            response.setHeaders(targetResponse.getAllHeaders());
            response.setEntity(targetResponse.getEntity());

            // 打印响应的状态行
            System.out.println("<< Response: " + response.getStatusLine());

            // 判断连接是否应保持活动状态，并在上下文中设置相应的属性
            final boolean keepalive = this.connStrategy.keepAlive(response, context);
            context.setAttribute(HTTP_CONN_KEEPALIVE, keepalive);
        }
    }


    // 定义一个静态的线程类，用于处理HTTP代理请求
    static class ProxyThread extends Thread {

        // 成员变量：HTTP服务接口、输入连接（来自客户端）和输出连接（到目标服务器）
        private final HttpService httpservice;
        private final DefaultBHttpServerConnection inconn;
        private final DefaultBHttpClientConnection outconn;

        // 构造函数，初始化线程所需的HTTP服务、输入连接和输出连接
        public ProxyThread(
                final HttpService httpservice,
                final DefaultBHttpServerConnection inconn,
                final DefaultBHttpClientConnection outconn) {
            super();
            this.httpservice = httpservice;
            this.inconn = inconn;
            this.outconn = outconn;
        }

        // 线程执行的主体方法
        @Override
        public void run() {
            System.out.println("New connection thread"); // 打印新线程开始的消息
            final HttpContext context = new BasicHttpContext(null); // 创建一个HTTP上下文对象

            // 将输入和输出连接对象绑定到执行上下文中，以便在后续处理中使用
            context.setAttribute(HTTP_IN_CONN, this.inconn);
            context.setAttribute(HTTP_OUT_CONN, this.outconn);

            try {
                // 循环处理请求，直到线程被中断或连接关闭
                while (!Thread.interrupted()) {
                    if (!this.inconn.isOpen()) { // 如果输入连接已关闭，则关闭输出连接并退出循环
                        this.outconn.close();
                        break;
                    }

                    // 处理来自客户端的HTTP请求，并将响应发送回客户端
                    this.httpservice.handleRequest(this.inconn, context);

                    // 检查是否应该保持连接（HTTP keep-alive）
                    final Boolean keepalive = (Boolean) context.getAttribute(HTTP_CONN_KEEPALIVE);
                    if (!Boolean.TRUE.equals(keepalive)) { // 如果不应该保持连接，则关闭输入和输出连接并退出循环
                        this.outconn.close();
                        this.inconn.close();
                        break;
                    }
                }
            } catch (final ConnectionClosedException ex) { // 捕获连接关闭异常，打印消息并退出循环
                System.err.println("Client closed connection");
            } catch (final IOException ex) { // 捕获I/O异常，打印错误消息并退出循环
                System.err.println("I/O error: " + ex.getMessage());
            } catch (final HttpException ex) { // 捕获HTTP异常，打印错误消息并退出循环
                System.err.println("Unrecoverable HTTP protocol violation: " + ex.getMessage());
            } finally {
                try {
                    this.inconn.shutdown(); // 尝试关闭输入连接，并忽略可能抛出的I/O异常
                } catch (final IOException ignore) {
                }
                try {
                    this.outconn.shutdown(); // 尝试关闭输出连接，并忽略可能抛出的I/O异常
                } catch (final IOException ignore) {
                }
            }
        }
    }

}